package cn.openea.perpartner.wxpay.service.impl;

import cn.openea.perpartner.wxpay.entity.BaseField;
import cn.openea.perpartner.wxpay.entity.PayChange;
import cn.openea.perpartner.wxpay.entity.PayOrder;
import cn.openea.perpartner.wxpay.entity.RefundOrder;
import cn.openea.perpartner.wxpay.mapper.PayChangeMapper;
import cn.openea.perpartner.wxpay.mapper.PayOrderMapper;
import cn.openea.perpartner.wxpay.mapper.RefundOrderMapper;
import cn.openea.perpartner.wxpay.rabbit.service.PublisherService;
import cn.openea.perpartner.wxpay.service.AuthService;
import cn.openea.perpartner.wxpay.service.ReflectObject;
import cn.openea.perpartner.wxpay.service.WxPayService;
import cn.openea.perpartner.wxpay.utils.Tools;
import cn.openea.perpartner.wxpay.vo.PayVo;
import cn.openea.perpartner.wxpay.vo.R;
import cn.openea.perpartner.wxpay.wxpaysdk.MyWxPayConfig;
import cn.openea.perpartner.wxpay.wxpaysdk.WXPay;
import cn.openea.perpartner.wxpay.wxpaysdk.WXPayConstants;
import cn.openea.perpartner.wxpay.wxpaysdk.WXPayUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @Name: 微信支付统一接口实现类
 * @author: Mr.Yun
 * @Created: 2022/10/13 22:30
 */
@Service
@Slf4j
public class WxPayServiceImpl implements WxPayService {

    @Autowired
    MyWxPayConfig config;

    @Autowired
    WXPay wxPay;

    @Autowired
    AuthService authService;

    @Autowired
    ReflectObject reflectObject;

    @Autowired
    PayChangeMapper payChangeMapper;

    @Autowired
    PayOrderMapper payOrderMapper;

    @Autowired
    RefundOrderMapper refundOrderMapper;

    @Autowired
    RedisTemplate<String, Object> redisTemplate;
    @Autowired
    PublisherService publisherService;
    @Value("${wxpay.notifyurl}")
    private String notifyurl;

    @Value("${wxpay.spbillip}")
    private String spbillip;

    // 使用轻量级的可重入锁处理并发通知的问题
    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public R createUnifiedorder(PayVo payVo) throws Exception {
        // 接口URL地址：https://api.mch.weixin.qq.com/pay/unifiedorder
        // 第1步：验证鉴权
        if(!authService.auth(payVo)){
            // 调用接口前的鉴权失败
            return R.error("鉴权失败").put("err_code_des", "参数被篡改或未授权或授权过期");
        }
        // 记录订单号是否是空的
        boolean isBlankNo = false;
        if(Tools.isBlank(payVo.getTradeNo())){
            payVo.setTradeNo(config.getMchID() + Tools.autoUserID(5));
            isBlankNo = true;
        }
        //log.info("鉴权后PayVo={}", payVo);

        // 第2步：组装请求数据 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
        String thisNotifyApiUrl = "ordernotify";
        Map<String, String> paydata = new HashMap<String, String>();
        paydata.put("body", payVo.getTradeDesc());//商品描述
        paydata.put("out_trade_no", payVo.getTradeNo());//商户订单号
        paydata.put("openid", payVo.getOpenid());//商户订单号
        paydata.put("total_fee", String.valueOf(payVo.getPayPrice()));//标价金额，单位分,int
        paydata.put("spbill_create_ip", spbillip);//终端IP
        paydata.put("notify_url", notifyurl + thisNotifyApiUrl);//通知地址
        paydata.put("trade_type", "JSAPI");//交易类型

        // 第3步：在发起微信支付前先保存订单数据
        PayOrder payOrder = new PayOrder();
        Map payVoToMap = reflectObject.payVoToMap(payVo);
        // 设置一些基本值
        payVoToMap.put("reqParams", paydata.toString());
        payVoToMap.put("tradeStatus", "N");
        payVoToMap.put("ordertype", "pay");
        Date now = new Date();
        payVoToMap.put("createdTime", now);
        payVoToMap.put("updateTime", now);
        // 通过反射给 payOrder 对象从payVoToMap设置值
        reflectObject.invoke(payVoToMap, payOrder);

        // 验证参数必填性
        R validata = authService.validata(payOrder);
        if(validata.getCode() == 500){
            return R.error().putAll(validata.getData());
        }

        // 在这里调用公共函数保存订单，这里需要判断是更新还是新插入数据到数据表
        R r = saveOrderData(isBlankNo, payOrderMapper, payOrder, false, payVo);
        if(r.getCode() == 500){
            return r;
        }

        // 这里判断当前的金额是多少不确定走不同的分支
        if("0".equals(payOrder.getPayPrice())){
            // 设置请求后的 resParams 参数
            payOrder.setResParams("{return_code=SUCCESS, return_msg=零元支付默认成功}");
            // 20221110 Add 返回信息一个订单号 tradeNo
            payOrder.setTradeStatus("S");
            payOrder.setResMsg("SUCCESS");
            // 第6步：在请求微信后，将支付结果写入之前保存订单数据中
            payOrder.setUpdateTime(new Date());
            payOrderMapper.updateById(payOrder);

            /**
             * 调用对应的业务处理支付成功后的动作  MQ
             * TODO 商品团购、样品领取、  ---》 MQ （传入对象 payOrder） --》 统一消费者
             *  这里有一个问题：就是支付 0 元时，是没有回调到这里的，这里有特殊的处理
             */

            publisherService.execute(payOrder);

            r = R.ok().put("hasNext", false).put("tradeNo", payVo.getTradeNo());
            return r;
        }

        // 第4步：发起接口进行统一支付，到了这一步基本表示当前支付金额不是0时，才请求微信进行支付
        Map<String, String> respData = wxPay.unifiedOrder(paydata);
        //log.info("发起统一支付后响应：respData={}", respData);
        // 设置请求后的 resParams 参数
        payOrder.setResParams(respData.toString());

        // 第5步：由于返回的respData与前端支付时的字段不尽相同，这里需要手动转换一下
        Map<String, String> payMapData = new HashMap<>();
        if("SUCCESS".equals(respData.get("result_code"))){
            // 进入这里表示统一请求成功了
            payMapData.put("appId", respData.get("appid"));
            payMapData.put("nonceStr", WXPayUtil.generateNonceStr());
            payMapData.put("package", "prepay_id=" + respData.get("prepay_id"));
            payMapData.put("signType", WXPayConstants.HMACSHA256);
            payMapData.put("timeStamp", String.valueOf(System.currentTimeMillis()/1000));
            String signStr = WXPayUtil.generateSignature(payMapData, config.getKey(), WXPayConstants.SignType.HMACSHA256);
            payMapData.put("paySign", signStr);
            // 20221110 Add 返回信息一个订单号 tradeNo
            r = R.ok().put("hasNext", true).put("tradeNo", payVo.getTradeNo()).putAll(payMapData);
            payOrder.setTradeStatus("N");
            payOrder.setResMsg("等待支付");
        } else {
            // 表示请求微信统一接口失败了
            String errmsg = respData.get("err_code_des");
            //log.info("errmsg = {}", errmsg);
            payMapData.putAll(respData);
            r = R.error(errmsg).putAll(payMapData).put("tradeNo", payVo.getTradeNo());
            //log.info("R = {}" , r);
            payOrder.setTradeStatus("E");
            payOrder.setResMsg(errmsg);
        }
        //log.info("封装前端支付参数：payMapData={}", payMapData);

        // 第6步：在请求微信后，将支付结果写入之前保存订单数据中
        payOrder.setUpdateTime(new Date());
        payOrderMapper.updateById(payOrder);
        return r;
    }

    @Override
    public String createOrderNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 支付结果通知 https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=9_7&index=8

        // 第1步：获取响应xml数据并解析成Map对象
        String bodyData = Tools.readRequestBodyData(request);//取到xml格式数据
        Map<String, String> respData = WXPayUtil.xmlToMap(bodyData);
        //log.info("支付成功回调参数解析.respData={}" , respData);
        // 如果没有成功返回的消息，表示处理失败，这里就不需要再响应了
        if(!"SUCCESS".equalsIgnoreCase(respData.get("result_code"))) {
            return getReqData(false, "Error：无响应参数");//返回错误信息
        }

        // 第2步：对数据进行验证签，防止数据被篡改
        String signStr = respData.get("sign");
        String signTypeStr = respData.get("sign_type");
        WXPayConstants.SignType signType = WXPayConstants.SignType.HMACSHA256;
        if(WXPayConstants.SignType.MD5.toString().equalsIgnoreCase(signTypeStr)){
            signType = WXPayConstants.SignType.MD5;
        }
        respData.remove("sign");
        String reSignStr = WXPayUtil.generateSignature(respData, config.getKey(), signType);
        //log.info("signStr = {}  reSignStr = {}", signStr ,reSignStr);

        if(!signStr.equalsIgnoreCase(reSignStr)){
            // 验证签名不通过
            return getReqData(false, "Error：验签不通过.");//返回错误信息
        }

        // 第3步：处理过程业务，这里要保证多次接口请求的幂等性
        String tradeNo = respData.get("out_trade_no");//获取商户订单号
        // 从redis中查是否有订单，如果有则直接返回
        if(redisTemplate.hasKey("order" + tradeNo)){
            return getReqData(true, null);
        }

        PayOrder payOrder = new PayOrder();
        payOrder.setTradeNo(tradeNo);
        LambdaQueryWrapper<PayOrder> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Tools.isNotBlank(tradeNo), PayOrder::getTradeNo, tradeNo);
        // 尝试获取可重入锁
        if(lock.tryLock()) {
            try {
                payOrder = payOrderMapper.selectOne(queryWrapper);
                if(payOrder == null){
                    return getReqData(false, "Error：未找到订单" + tradeNo);
                }
                //log.info("lock.tryLock().payOrder = {}", payOrder);
                // 在Redis中设置一条记录，时长为5分钟
                redisTemplate.opsForValue().set("order" + tradeNo, "SUCCESS", 5, TimeUnit.MINUTES);

                // 设置订单状态 和 状态消息
                payOrder.setTradeStatus("S");
                payOrder.setResMsg(respData.get("result_code"));
                payOrder.setSignStr(signStr);//设置返回签名
                payOrder.setUpdateTime(new Date());//设置最后更新时间
                payOrderMapper.updateById(payOrder);

                /**
                 * 调用对应的业务处理支付成功后的动作  MQ
                 * TODO 商品团购、样品领取、  ---》 MQ （传入对象 payOrder） --》 统一消费者
                 *  这里有一个问题：就是支付 0 元时，是没有回调到这里的，这里有特殊的处理
                 */
                publisherService.execute(payOrder);
            } finally {
                lock.unlock();//这里需要手动释放锁
            }
        }
        // 最后：响应成功应答
        String reqBody = getReqData(true, null);
        return reqBody;
    }

    @Override
    public R queryOrderByTradeNo(PayVo payVo) throws Exception {
        // 接口URL地址：https://api.mch.weixin.qq.com/pay/orderquery
        //log.info("PayVo={}", payVo);

        if(!authService.auth(payVo)){
            // 调用接口前的鉴权失败
            return R.error("鉴权失败").put("err_code_des", "参数被篡改或未授权或授权过期");
        }
        // 20221110 添加参数必填写验证
        if(Tools.isBlank(payVo.getTradeNo())){
            return R.error("缺少必要参数").put("err_code_des", "缺少必要参数：tradeNo，请仔细核查.");
        }

        // 组装请求数据 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_2
        Map<String, String> querydata = new HashMap<String, String>();
        querydata.put("appid", config.getAppID());
        querydata.put("mch_id", config.getMchID());
        querydata.put("out_trade_no", payVo.getTradeNo());//商户订单号
        querydata.put("nonce_str", WXPayUtil.generateNonceStr());//随机字符串String(32)
        //生成签名
        String signStr = WXPayUtil.generateSignature(querydata, config.getKey());
        querydata.put("sign", signStr);//签名String(32)
        Map<String, String> respData = wxPay.orderQuery(querydata);
        // 构建返回体
        if(respData != null && "SUCCESS".equalsIgnoreCase(respData.get("result_code"))){
            return R.ok("查询成功").putAll(respData);
        }
        return R.error(respData.get("err_code_des")).putAll(respData);
    }

    @Override
    public R queryOrderByTradeNoIsok(PayVo payVo) throws Exception {
        R r = queryOrderByTradeNo(payVo);
        if(r.getCode() == 500) {
            return r;
        }
        /**
         * 进入到这里的都是查询成功的记录
         *  交易状态 SUCCESS--支付成功 REFUND--转入退款 NOTPAY--未支付 CLOSED--已关闭 REVOKED--已撤销(刷卡支付)
         *      USERPAYING--用户支付中 PAYERROR--支付失败(其他原因，如银行返回失败) ACCEPT--已接收，等待扣款
         */
        String tradeState = (String) r.getData().get("trade_state");
        String tradeStateDesc = (String) r.getData().get("trade_state_desc");
        if(Tools.isNotBlank(tradeStateDesc)){
            r.setMsg(tradeStateDesc);
        } else {
            r.setMsg(tradeState);
        }
        // 只有下面这两种状态,表示订单是支付成功的
        if("SUCCESS".equalsIgnoreCase(tradeState) || "REFUND".equalsIgnoreCase(tradeState)){
            return r;
        }
        r.setCode(201);//设置错误码201表示订单支付失败
        return r;
    }

    @Override
    public R refundByTradeNo(PayVo payVo) throws Exception {
        // 接口URL地址：https://api.mch.weixin.qq.com/secapi/pay/refund
        R r = R.error();
        //log.info("PayVo={}", payVo);

        // 第1步：验证鉴权
        if(!authService.auth(payVo)){
            // 调用接口前的鉴权失败
            return r.error("鉴权失败").put("err_code_des", "参数被篡改或未授权或授权过期");
        }

        boolean isBlankNo = false;
        // 如果退款单号为空，则这里需要给一个默认单号
        if(Tools.isBlank(payVo.getRefundNo())){
            payVo.setRefundNo(config.getMchID() + Tools.autoUserID(5));
            isBlankNo = true;
        }
        //log.info("鉴权后PayVo={}", payVo);

        // 第2步：组装请求数据 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_4
        String thisNotifyApiUrl = "refundnotify";
        Map<String, String> refundata = new HashMap<String, String>();
        refundata.put("appid", config.getAppID());//公众账号ID，这里可以认为是小程序id，你需要操作哪个小程序中的订单进行退款操作
        refundata.put("mch_id", config.getMchID());//商户号
        refundata.put("nonce_str", WXPayUtil.generateNonceStr());//随机字符串String(32)
        refundata.put("out_trade_no", payVo.getTradeNo());//这里使用商户订单号，因为支付时记录的是商户订单号
        //商户系统内部的退款单号，商户系统内部唯一，只能是数字、大小写字母_-|*@ ，同一退款单号多次请求只退一笔
        refundata.put("out_refund_no", payVo.getRefundNo());
        refundata.put("total_fee", String.valueOf(payVo.getPayPrice()));//订单金额
        refundata.put("refund_fee", String.valueOf(payVo.getRefundPrice()));//退款金额
        refundata.put("refund_desc", payVo.getTradeDesc());//退款原因
        refundata.put("notify_url", notifyurl + thisNotifyApiUrl);//退款结果通知url
        // 生成签名
        String signStr = WXPayUtil.generateSignature(refundata, config.getKey());
        refundata.put("sign", signStr);//签名String(32)
        //log.info("请求退款调用前组装数据refundata={}", refundata);

        // 第3步：在发起微信支付前先保存订单数据
        RefundOrder refundOrder = new RefundOrder();
        Map payVoToMap = reflectObject.payVoToMap(payVo);
        // 设置一些基本值
        payVoToMap.put("signStr", signStr);
        payVoToMap.put("reqParams", refundata.toString());
        payVoToMap.put("tradeStatus", "N");
        payVoToMap.put("ordertype", "refund");
        Date now = new Date();
        payVoToMap.put("createdTime", now);
        payVoToMap.put("updateTime", now);
        // 通过反射给 payOrder 对象从payVoToMap设置值
        reflectObject.invoke(payVoToMap, refundOrder);

        // 验证参数必填性
        R validata = authService.validata(refundOrder);
        if(validata.getCode() == 500){
            return r.error().putAll(validata.getData());
        }

        // 在这里调用公共函数保存订单，这里需要判断是更新还是新插入数据到数据表
        r = saveOrderData(isBlankNo, refundOrderMapper, refundOrder, true, payVo);
        if(r.getCode() == 500) {
            return r;
        }

        // 第4步：发起退款操作
        Map<String, String> respData = wxPay.refund(refundata);
        //log.info("向微信请求退款结果respData={}", respData);
        // 设置请求后的 resParams 参数
        refundOrder.setResParams(respData.toString());

        // 第5步：判断请求是否成功
        if("SUCCESS".equals(respData.get("result_code"))){
            // 进入这里表示统一请求成功了
            r = R.ok("已发起退款申请").putAll(respData).put("tradeNo", payVo.getTradeNo());
            refundOrder.setTradeStatus("W");
            refundOrder.setResMsg("已发起退款申请，等待微信响应...");
        } else {
            // 表示请求微信统一接口失败了
            String errmsg = respData.get("err_code_des");
            r = R.error(errmsg).putAll(respData).put("tradeNo", payVo.getTradeNo());
            refundOrder.setTradeStatus("E");
            refundOrder.setResMsg(errmsg);
        }

        // 第6步：在请求微信后，将支付结果写入之前保存订单数据中
        refundOrder.setUpdateTime(new Date());
        refundOrderMapper.updateById(refundOrder);
        return r;
    }

    @Override
    public String refundOrderNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 退款结果通知 https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=9_16&index=10

        // 第1步：获取响应xml数据并解析成Map对象
        String bodyData = Tools.readRequestBodyData(request);//取到xml格式数据
        Map<String, String> respData = WXPayUtil.xmlToMap(bodyData);
        //log.info("微信退款回调.respData={}", respData);
        // 如果没有成功返回的消息，表示处理失败，这里就不需要再响应了
        if(!"SUCCESS".equalsIgnoreCase(respData.get("return_code"))) {
            return getReqData(false, respData.get("return_msg"));//返回错误信息
        }

        /**
         * 第2步：调用解密算法，进行解密字符串，解密步骤如下：
         * （1）对加密串A做base64解码，得到加密串B
         * （2）对商户key做md5，得到32位小写key* ( key设置路径：微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置 )
         * （3）用key*对加密串B做AES-256-ECB解密（PKCS7Padding）
         */
        String reqInfo = respData.get("req_info");
        String decryptData = Tools.decryptWxData(reqInfo, Tools.md5(config.getKey()));
        decryptData = decryptData.replace("<root>", "<xml>").replace("</root>", "</xml>");
        respData = WXPayUtil.xmlToMap(decryptData);
        //log.info("解密后的参数.respData={}", respData);

        // 第3步：处理过程业务，这里要保证多次接口请求的幂等性
        String refundNo = respData.get("out_refund_no");//获取商户退款单号
        String refundStatus = respData.get("refund_status");//获取退款状态
        if(Tools.isBlank(refundNo) || Tools.isBlank(refundStatus)){
            return getReqData(false, "Error：返回串中缺少必要的参数");
        }
        // 从redis中查是否有订单，如果有则直接返回
        if(redisTemplate.hasKey("refundNo" + refundNo)){
            return getReqData(true, null);
        }

        RefundOrder refundOrder = new RefundOrder();
        refundOrder.setRefundNo(refundNo);
        LambdaQueryWrapper<RefundOrder> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Tools.isNotBlank(refundNo), RefundOrder::getRefundNo, refundNo);
        // 尝试获取可重入锁
        if(lock.tryLock()) {
            try {
                refundOrder = refundOrderMapper.selectOne(queryWrapper);
                if(refundOrder == null){
                    return getReqData(false, "Error：未找到退款订单" + refundNo);
                }
                // 在Redis中设置一条记录，时长为5分钟
                redisTemplate.opsForValue().set("refundNo" + refundNo, "SUCCESS", 5, TimeUnit.MINUTES);

                // 依据状态，设置订单状态 和 状态消息 | SUCCESS-退款成功、CHANGE-退款异常、REFUNDCLOSE—退款关闭
                if("SUCCESS".equalsIgnoreCase(refundStatus)){
                    refundStatus += "-退款成功";
                } else if("CHANGE".equalsIgnoreCase(refundStatus)){
                    refundStatus += "-退款异常";
                } else if("REFUNDCLOSE".equalsIgnoreCase(refundStatus)){
                    refundStatus += "-退款关闭";
                }
                refundOrder.setResMsg(refundStatus);
                refundOrder.setTradeStatus(String.valueOf(refundStatus.charAt(0)));
                refundOrder.setUpdateTime(new Date());//设置最后更新时间
                refundOrderMapper.updateById(refundOrder);
            } finally {
                lock.unlock();//这里需要手动释放锁
            }
        }
        // 最后：响应成功应答
        String reqBody = getReqData(true, null);
        return reqBody;
    }

    @Override
    public R payToPersonalChange(PayVo payVo) throws Exception {
        // 接口URL地址：https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers
        R r = R.error();
        //log.info("PayVo={}", payVo);

        if(!authService.auth(payVo)){
            // 调用接口前的鉴权失败
            return R.error("鉴权失败").put("err_code_des", "参数被篡改或未授权或授权过期");
        }

        boolean isBlankNo = false;
        // 如果订单号为空，则这里需要给一个默认单号
        if(Tools.isBlank(payVo.getTradeNo())){
            payVo.setTradeNo(config.getMchID() + Tools.autoUserID(5));
            isBlankNo = true;
        }
        //log.info("鉴权后PayVo={}", payVo);

        // 请求接口地址
        String urlSuffix = "/mmpaymkttransfers/promotion/transfers";
        // 组装请求数据 https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_2
        Map<String, String> paydata = new HashMap<String, String>();
        paydata.put("mch_appid", config.getAppID());//公众账号ID，这里可以认为是小程序id
        paydata.put("mchid", config.getMchID());//商户号
        paydata.put("partner_trade_no", payVo.getTradeNo());//商户订单号，需保持唯一性(只能是字母或者数字，不能包含有其它字符)(32)
        paydata.put("nonce_str", WXPayUtil.generateNonceStr());//随机字符串String(32)
        paydata.put("openid", payVo.getOpenid());//用户openid,在appid下的用户编号
        paydata.put("check_name", "NO_CHECK");//校验用户姓名选项	NO_CHECK：不校验真实姓名 FORCE_CHECK：强校验真实姓名
        paydata.put("amount", String.valueOf(payVo.getPayPrice()));//付款金额，单位分,int
        paydata.put("desc", payVo.getTradeDesc());//付款备注String(100)
        //生成签名
        String signStr = WXPayUtil.generateSignature(paydata, config.getKey());
        paydata.put("sign", signStr);//签名String(32)

        // 在发起微信支付前先保存订单数据
        PayChange payChange = new PayChange();
        Map payVoToMap = reflectObject.payVoToMap(payVo);
        // 设置一些基本值
        payVoToMap.put("signStr", signStr);
        payVoToMap.put("reqParams", paydata.toString());
        payVoToMap.put("tradeStatus", "N");
        payVoToMap.put("ordertype", "change");
        Date now = new Date();
        payVoToMap.put("createdTime", now);
        payVoToMap.put("updateTime", now);
        // 通过反射给payChange对象从payVoToMap设置值
        reflectObject.invoke(payVoToMap, payChange);

        // 验证参数必填性
        R validata = authService.validata(payChange);
        if(validata.getCode() == 500){
            return R.error().putAll(validata.getData());
        }

        // 在这里调用公共函数保存订单，这里需要判断是更新还是新插入数据到数据表
        r = saveOrderData(isBlankNo, payChangeMapper, payChange, false, payVo);
        if(r.getCode() == 500) {
            return r;
        }

        // 发起接口支付，支出现金到个人零钱
        String xmlStr = wxPay.requestWithCert(urlSuffix, paydata, config.getHttpConnectTimeoutMs(), config.getHttpReadTimeoutMs());

        // 解析返回的状态
        Map<String, String> respData = WXPayUtil.xmlToMap(xmlStr);
        // 设置请求后的 resParams 参数
        payChange.setResParams(respData.toString());

        // partner_trade_no	这个字段是在 return_code 和result_code都为SUCCESS的时候有返回，并且肯定是我们传入的订单号，所以支付状态肯定是成功的了
        if(payVo.getTradeNo().equalsIgnoreCase(respData.get("partner_trade_no"))){
            // 付款成功，返回的微信付款单号 和 付款成功时间
            String paymentNo = respData.get("payment_no");
            String paymentTime = respData.get("payment_time");
            r = R.ok("付款到零钱成功").putAll(respData).put("paymentNo", paymentNo).put("paymentTime", paymentTime);
            payChange.setTradeStatus("S");
            payChange.setResMsg("付款到零钱成功");
        } else {
            String errmsg = respData.get("err_code_des");
            // 支付结果，错误的情况下
            if(Tools.isBlank(errmsg)){
                errmsg = respData.get("return_msg");
            }
            r = R.error(errmsg).putAll(respData);
            payChange.setTradeStatus("E");
            payChange.setResMsg(errmsg);
        }
        // 设置更新时间
        payChange.setUpdateTime(new Date());
        // 在请求微信后，将支付结果写入之前保存订单数据中
        payChangeMapper.updateById(payChange);
        return r;
    }

    /**
     * 在处理业务前先检查订单是否存在再决定是更新还是插入数据
     * @param isBlankNo 订单号是否为空，如果是退款单就是退款单号是否为空，true为空
     * @param baseMapper 操作数据库的Mapper父类
     * @param payobj 当前需要保存的数据对象，这里使用父类是更方便统一
     * @param isRefund 是否是退款单号
     * @param payVo 前端传入的值对象
     * @return
     */
    private R saveOrderData(boolean isBlankNo, BaseMapper baseMapper, BaseField payobj, boolean isRefund, PayVo payVo) {
        // 在这里应该先看本地是否有相应的订单
        if(!isBlankNo) {
            QueryWrapper<BaseField> queryWrapper = new QueryWrapper();
            if(isRefund){
                queryWrapper.eq("refundNo", payVo.getRefundNo());//退款订单
            } else {
                queryWrapper.eq("tradeNo", payVo.getTradeNo());//支付订单，包含零钱订单
            }
            BaseField oldPayChange = (BaseField) baseMapper.selectOne(queryWrapper);//查询对象
            if(oldPayChange != null){
                if("S".equalsIgnoreCase(oldPayChange.getTradeStatus())){
                    return R.error("无权重入完成的订单").put("err_code_des", "单号的历史状态为已完成不允许重入，请更换单号重试.");
                }
                payobj.setId(oldPayChange.getId());
            }
        }

        // 20221122 在这里统一检查 imageUrl 这个字段，只取第1个数据，因类前端会有多组图片
        String tmpurl = payobj.getImageUrl();
        if(Tools.isNotBlank(tmpurl)){
            String[] splitStr = tmpurl.split(",");
            tmpurl = splitStr[0];
            payobj.setImageUrl(tmpurl);
        }

        // 写入数据库中
        if(payobj.getId() != null){
            if(baseMapper.updateById(payobj) != 1){
                return R.error("更新数据库记录出错").put("err_code_des", "可能缺少必要参数或参数有误，请仔细核查.");
            }
        } else {
            if(baseMapper.insert(payobj) != 1){
                return R.error("写入数据库记录出错").put("err_code_des", "可能缺少必要参数或参数有误，请仔细核查.");
            }
        }
        return R.ok();
    }

    /**
     * 封装响应微信回调接口的字符串
     * @param isok 是否成功标记，true表示成功，false表示失败
     * @param msg 响应的消息，若为空，只自动填充
     * @return
     * @throws Exception
     */
    private String getReqData(boolean isok, String msg) throws Exception {
        String code = "SUCCESS", tmpmsg = "OK";
        Map<String, String> reqData = new HashMap<>();
        if(!isok){
            code = "FAIL";
            tmpmsg = "Error：处理错误";
        }
        reqData.put("return_code", code);//返回状态码：SUCCESS/FAIL
        reqData.put("return_msg", Tools.isBlank(msg) ? tmpmsg : msg);//返回信息：返回信息，如非空，为错误原因：签名失败/参数格式校验错误
        return WXPayUtil.mapToXml(reqData);
    }

}
